package edu.washington.cs.gitanaly;

import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.TypeDeclaration;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;


class MyMethod {
	private String prefix;
	private String name;
	private String content;
	
	public MyMethod(String prefix, String name, String content) {
		this.prefix = prefix;
		this.name = name;
		this.content = content;
	}
	
	public String getName() {
		return name;
	}
	
	public String getPrefix() {
		return prefix;
	}
	
	public String getContent() {
		return content;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof MyMethod)) {
			return false;
		}
		
		MyMethod m = (MyMethod) obj;
		
		return prefix.equals(m.prefix) && content.equals(m.content);
	}
}

public class Util {
	
	private static boolean checkCommit(Repository repo, RevWalk revWalk, RevCommit commit, String path, String typeName, String methodName)
			throws MissingObjectException, IncorrectObjectTypeException, IOException {
		
		TreeWalk treeWalk = new TreeWalk(repo);

		treeWalk.addTree(commit.getTree());

		if (commit.getParentCount() > 0) {
			RevCommit parent = commit.getParent(0);
			parent = revWalk.parseCommit(parent.getId());
			treeWalk.addTree(parent.getTree());
		}
		
//		for (RevCommit parent : commit.getParents()) {
//			RevCommit parentCommit = revWalk.parseCommit(parent.getId());
//			treeWalk.addTree(parentCommit.getTree());
//			break;
//		}
		
		treeWalk.setRecursive(true);
		treeWalk.setFilter(AndTreeFilter.create(
				new MethodDiffFilter(repo, path, typeName, methodName),
				PathFilter.create(path)));
		
//		treeWalk.setFilter(new MethodDiffFilter(repo, path, methodName));
//		treeWalk.setFilter(AndTreeFilter.create(
//				new AllDiffFilter(), PathFilter.create(path)));
		
		return treeWalk.next();
		
//		boolean flag = false;
//		while (treeWalk.next()) {
//			System.out.println(treeWalk.getPathString());
//			System.out.println(treeWalk.getObjectId(0).name());
//			flag = true;
//
//			ObjectLoader loader = repo.open(treeWalk.getObjectId(0));
//			
//			InputStream stream = loader.openStream();
//			
//			CompilationUnit cu = null;
//			
//			try {
//				cu = JavaParser.parse(stream);
//			} catch (ParseException e) {
//				e.printStackTrace();
//			} finally {
//				stream.close();
//			}
//			
//			new VoidVisitorAdapter<Object>() {
//				public void visit(japa.parser.ast.body.ClassOrInterfaceDeclaration n, Object arg) {
//					System.out.println(n.getName());
//					super.visit(n, arg);
//				};
//				
//				public void visit(MethodDeclaration n, Object arg) {
//					System.out.print(n.getType() + " " + n.getName() + "(");
//					
//					if (n.getParameters() != null) {
//						int i = 0;
//						for (Parameter para : n.getParameters()) {
//							if (i > 0) {
//								System.out.print(", ");
//								++i;
//							}
//							System.out.print(para.toString());
//						}
//					}
//					
//					System.out.println(")");
//				};
//			}.visit(cu, null);
//			
//		}
//		
//		if (flag) {
//			System.out.println(commit.getTree().name());
//		}
//		
//		return flag;
	}
	
	public static void diffMethods(Repository repo, RevWalk revWalk, RevCommit commit)
			throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
		
		TreeWalk treeWalk = new TreeWalk(repo);

		treeWalk.addTree(commit.getTree());
		
		RevCommit parent = commit.getParent(0);
		if (parent != null) {
			parent = revWalk.parseCommit(parent.getId());
			treeWalk.addTree(parent.getTree());
		}
		
		treeWalk.setRecursive(true);
		treeWalk.setFilter(new AllDiffFilter());
//		treeWalk.setFilter(new AllDiffFilter());
		
		while (treeWalk.next()) {
			if (!treeWalk.getPathString().endsWith(".java")) {
				continue;
			}
			
			if (treeWalk.getTreeCount() == 1) {
			} else {
				
				ArrayList<ArrayList<MyMethod>> mss = new ArrayList<ArrayList<MyMethod>>();
				
				for (int nth = 0; nth < 2; ++nth) {
					mss.add(new ArrayList<MyMethod>());

					if (!repo.hasObject(treeWalk.getObjectId(nth))) {
						continue;
					}
					
//					if (treeWalk.getObjectId(nth)) {
//						
//					}
					
					ObjectLoader loader = repo.open(treeWalk.getObjectId(nth));
					InputStream stream = loader.openStream();
					
					BufferedReader in = new BufferedReader(new InputStreamReader(stream));
					
					StringBuffer stringBuffer = new StringBuffer();
					while (true) {
						String line = in.readLine();
						if (line == null) {
							break;
						}
						
						stringBuffer.append(line.replaceAll("enum", "ennum"));
						stringBuffer.append("\n");
					}
					in.close();
					
					CompilationUnit cu = null;
					
					try {
						cu = JavaParser.parse(new ByteArrayInputStream(stringBuffer.toString().getBytes()));
					} catch (ParseException e) {
						e.printStackTrace();
					} finally {
						
					}
					
			        List<TypeDeclaration> types = cu.getTypes();
			        
			        for (TypeDeclaration type : types) {
			            List<BodyDeclaration> members = type.getMembers();
			            for (BodyDeclaration member : members) {
			                if (member instanceof MethodDeclaration) {
			                    MethodDeclaration method = (MethodDeclaration) member;
			                    mss.get(nth).add(new MyMethod(
			                    		cu.getPackage().getName() + "." + type.getName(), method.getName(), method.toString()));
			                }
			            }
			        }
				}
				
				ArrayList<MyMethod> ms0 = mss.get(0);
				ArrayList<MyMethod> ms1 = mss.get(1);
				
				for (MyMethod m : ms0) {
					boolean changed = true;
					for (MyMethod mm : ms1) {
						if (m.getPrefix().equals(mm.getPrefix()) && m.getName().equals(mm.getName())) {
							if (m.equals(mm)) {
								changed = false;
							} else {
//								System.out.println("==================");
//								System.out.println(m.getContent().toString());
//								System.out.println("vvvvvvvvvvvvvvvvvv");
//								
//								System.out.println(mm.getContent().toString());
//								System.out.println("==================");
							}
							break;
						}
					}
					
					if (changed) {
						System.out.println(m.getPrefix() + "." + m.getName());
					}
				}
			
			}
		}
	}

	public static void findMethods(Repository repo, String tagA, String tagB, String path, String typeName, String methodString) {
		try {
			RevWalk revWalk = new RevWalk(repo);
			RevCommit rootCommit = revWalk.parseCommit(repo.resolve(tagB));
			revWalk.sort(RevSort.COMMIT_TIME_DESC);
			revWalk.markStart(rootCommit);
			
			int ta = new RevWalk(repo).parseCommit(repo.resolve(tagA)).getCommitTime();
			
			for (RevCommit commit : revWalk) {
				if (commit.getCommitTime() < ta) {
					break;
				}
				
				if (checkCommit(repo, revWalk, commit, path, typeName, methodString)) {
//					System.out.println(commit.name());
					diffMethods(repo, revWalk, commit);
				}
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
	public static void showAllRefs(Repository repo) {
		for (String key : repo.getAllRefs().keySet()) {
			System.out.println(key);
		}
	}
	
	public static void cat(Repository repo, TreeWalk treeWalk) {
		try {
			ObjectLoader loader = repo.open(treeWalk.getObjectId(0));
			InputStream stream = loader.openStream();
			BufferedReader in = new BufferedReader(new InputStreamReader(stream));
			
			while (true) {
				String line = in.readLine();
				if (line == null) {
					break;
				}
				System.out.println(line);
			}
			in.close();
		} catch (MissingObjectException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static boolean hasThisMethod1(Repository repo, TreeWalk treeWalk, int nth, String typeName, String methodName) {
		boolean found = false;
		try {
			ObjectLoader loader = repo.open(treeWalk.getObjectId(nth));
			BufferedReader in = new BufferedReader(new InputStreamReader(loader.openStream()));
			while (true) {
				String line = in.readLine();
				if (line == null) {
					break;
				}
				if (line.indexOf(methodName) >= 0) {
					found = true;
					break;
				}
			}
			in.close();
		} catch (Throwable e) {
		}
		return found;
	}

	public static boolean hasThisMethod(Repository repo, TreeWalk treeWalk, int nth, String tp, String methodName) {
		boolean found = false;
		try {
			ObjectLoader loader = repo.open(treeWalk.getObjectId(nth));
			InputStream stream = loader.openStream();
			
			BufferedReader in = new BufferedReader(new InputStreamReader(stream));
			
			StringBuffer stringBuffer = new StringBuffer();
			while (true) {
				String line = in.readLine();
				if (line == null) {
					break;
				}
				
				stringBuffer.append(line.replaceAll("enum", "ennum"));
				stringBuffer.append("\n");
			}
			in.close();
			
			CompilationUnit cu = null;
			
			try {
				cu = JavaParser.parse(new ByteArrayInputStream(stringBuffer.toString().getBytes()));
			} catch (ParseException e) {
				e.printStackTrace();
			} finally {
				
			}
			
	        List<TypeDeclaration> types = cu.getTypes();
	        for (TypeDeclaration type : types) {
	        	if (type.getName().equals(tp)) {
	        		found = hasMethod(type, methodName);
	        	} else {
		            List<BodyDeclaration> members = type.getMembers();
		            for (BodyDeclaration member : members) {
		                if (member instanceof TypeDeclaration) {
		                    TypeDeclaration subType = (TypeDeclaration) member;
		                    if (subType.getName().equals(tp)) {
		                    	found = hasMethod(subType, methodName);
		                    	break;
		                    }
		                }
		            }
	        	}
	        }
		} catch (Throwable e) {
		}
		return found;
	}
	
	private static boolean hasMethod(TypeDeclaration type, String methodName) {
        List<BodyDeclaration> members = type.getMembers();
        for (BodyDeclaration member : members) {
            if (member instanceof MethodDeclaration) {
                MethodDeclaration method = (MethodDeclaration) member;
                if (method.getName().equals(methodName)) {
                	return true;
                }
            }
        }
        return false;
	}
}
